Skip to main content

Sass 的核心用法

· 17 min read

总结 Sass 的核心用法

选择器嵌套与 & 标识符

// 节点
.wrap {
border: black 1px solid;

// 节点
& > ul:last-child {
border: black 1px solid;
}

// 节点(无样式属性)
div:first-child {

// 节点
&.intro > p {
color: skyblue;

// 节点
&:hover {
border: black 1px solid;
}

}

// 节点
&:hover {
background: green;
}

}
}

Tip:

  • 为了方便描述,我们把整个 scss 内的样式结构看作是一个森林,最外层的选择器就是每棵树的根节点,内部的每一层选择器就是一个节点。

现在我们需要知道如下几个要点:

  • scss 中的每个选择器当作一个节点
  • 整个编译的过程如下:
    • 以每棵树的根节点为单位,用一个指针指向树的根节点
    • 以前序遍历的方式(根左右)遍历每个节点
    • 遇到内部 有样式属性的节点 则停下,记录一下从根节点到当前节点到路径,这个路径实际上就是编译为 css 的结果
      • 需要注意的是,指针停下来时,如果当前节点中含有 & 标识符,那么 & 标识符会被替换为路径中的上一个节点
      • 如果不含 & 标识符,则会和上一个节点构成 后代选择器

下面我们一步步来分析上面的 scss 代码如何编译为 css 代码。

最开头的 scss 样式的树结构如下:

pic.nm

根据上面的要点,我们就可以很容易的写出如下 scss 编译后的内容:

.wrap {}
.wrap > ul:last-child {}
.wrap div:first-child.intro > p {}
.wrap div:first-child.intro > p:hover {}
.wrap div:first-child:hover {}

注释

scss 中有两种注释:

  • /* 注释 */,最终会被编译到 css 文件中
  • // 注释,不会被编译到 css 文件中

变量

语法:$key: value,其中 key 为变量名,value 为变量值。

$width: 5em;

#main {
width: $width;
}

并且变量还能声明在选择器内部,我们把这种变量称为局部变量:

#main {
$width: 5em;
width: $width;
}

#second {
width: $width; // 错误的,因为无法获取到 #main 选择器内部的局部变量
}

给变量添加 !global 声明可以将该变量转为全局变量,当然只局限于该 .scss 文件中。

#main {
$width: 5em !global;
width: $width;
}

#second {
width: $width; // 可以获取到 $width 变量
}

在给变量赋值时添加 !default 可以防止此次赋值覆盖掉之前取值非 null 的赋值。

$content: "First content";
$content: "Second content?" !default;

#main {
content: $content;
}

编译为:

#main {
content: "First content";
}

插槽

语法:#{},一般用于在 选择器属性名中插入变量。

$name: foo;
$attr: border;
p.#{$name} {
#{$attr}-color: blue;
}

数据类型及其常用函数

概览

scss 中支持 6 种数据类型值:

  • 数字:1, 2, 2em, 3px, 10%...
  • 字符串:'foo', "bar", baz
  • 颜色:blue, #ffffff, rgba(255,0,0,0.5)
  • 布尔类型:true, false
  • 空值:null
  • 数组:用空格或者逗号分隔,建议用逗号分隔 2px, 3px, 2em, 20%
  • 键值对:(key1: value1, key2: value2, key3: value3)

SassScript 也支持其他 CSS 属性值,比如 Unicode 字符集,或 !important 声明。然而Sass 不会特殊对待这些属性值,一律视为无引号字符串。

字符串

分为两类:

  • 有引号字符串,可以是单引号或双引号,比如:"Lucida Grande"'http://sass-lang.com'
  • 无引号字符串,比如:sans-serifbold

使用 #{} 插入的字符串最终都会被编译为无符号字符串。

数组

语法

  • 以逗号分隔:$colors: (red, blue, skyblue, green),建议用这个
  • 以空格分隔:$colors: (red blue skyblue green)

基本用法

$colors: (blue, green, skyblue, red);

@each $color in $colors {
.#{$color}-color {
color: $color;
}
}

编译为:

.blue-color {
color: blue;
}

.green-color {
color: green;
}

.skyblue-color {
color: skyblue;
}

.red-color {
color: red;
}

二维数组

$borders: ((1px solid black), (1px dot green));

@each $border in $borders {
.#{nth($border, 3)}-#{nth($border, 3)}-border {
border: $border;
}
}

编译为:

.solid-black-border {
border: 1px solid black;
}

.dot-green-border {
border: 1px dotted green;
}

Sass 中有关处理数组的常用内置函数

nth($list, index)

获取数组指定下标的值。

length($list)

获取指定数组长度。

append($list, $val, $separator: auto)

向数组中添加元素,$separator 可以缺省。

join($list1, $list2, $separator: auto, $bracketed: auto)

合并两个数组,$separator$bracketed 可以缺省。

index($list, $value)

获取数组指定值的下标。

另外还有一些处理数组元素分隔符的函数,具体可以参考 官方文档

注意:

  • Sass 中的数组下标是从 1 开始的。
  • $separator 的取值可以是 comma(逗号), space(空格), or slash(短斜线)

键值对

语法

$map: (
"name": kll,
"gender": boy
)

注意这里的 key 是字符串类型,可以带引号也可以不带。

遍历

@each

$radius-sizes: (
small-radius: 5px,
normal-radius: 10px,
large-radius: 15px
);

@each $key, $value in $radius-sizes {
.#{$key} {
border-radius: $value;
}
}

@for

$radius-sizes: (
small-radius: 5px,
normal-radius: 10px,
large-radius: 15px
);

@for $i from 1 through length($radius-sizes) {
$keys: map-keys($radius-sizes);
$cur-key: nth($keys, $i);

.#{$cur-key} {
border-radius: map-get($radius-sizes, $cur-key);
}
}
  • map-keys,传入一个键值对,获取该键值对的键值列表
  • map-get,第一个参数为键值对,后续传入一个 key,获取 value
  • nth,不仅可以计算列表长度,也可以计算键值对长度

常用函数

以下函数使用前需要引入:

@use "sass:map";

map.getmap.set

map.get 等价于 map-get,前者需要 @use "sass:map",后者则不需要。

$font-weights: ("regular": 400, "medium": 500, "bold": 700);

@debug map.get($font-weights, "medium"); // 500
@debug map.get($font-weights, "extra-bold"); // null
$font-weights: ("regular": 400, "medium": 500, "bold": 700);

@debug map.set($font-weights, "regular", 300);
// ("regular": 300, "medium": 500, "bold": 700)

map.keysmap.values

这两个都可以写成 map-keysmap-values

$font-weights: ("regular": 400, "medium": 500, "bold": 700);

@debug map.keys($font-weights); // "regular", "medium", "bold"
$font-weights: ("regular": 400, "medium": 500, "bold": 700);

@debug map.values($font-weights); // 400, 500, 700

map.has-key

$font-weights: ("regular": 400, "medium": 500, "bold": 700);

@debug map.has-key($font-weights, "regular"); // true
@debug map.has-key($font-weights, "bolder"); // false

map.mergemap.deep-merge

$helvetica-light: (
"weights": (
"lightest": 100,
"light": 300
)
);
$helvetica-heavy: (
"weights": (
"medium": 500,
"bold": 700
)
);

@debug map.deep-merge($helvetica-light, $helvetica-heavy);
// (
// "weights": (
// "lightest": 100,
// "light": 300,
// "medium": 500,
// "bold": 700
// )
// )
@debug map.merge($helvetica-light, $helvetica-heavy);
// (
// "weights": (
// "medium: 500,
// "bold": 700
// )
// )

map.removemap.deep-remove

$fonts: (
"Helvetica": (
"weights": (
"regular": 400,
"medium": 500,
"bold": 700
)
)
);

@debug map.deep-remove($fonts, "Helvetica", "weights", "regular");
// (
// "Helvetica": (
// "weights: (
// "medium": 500,
// "bold": 700
// )
// )
// )

运算

所有的数据类型都支持 ==!= 的运算。

数字类型运算

Sass 中支持 +, -, *, /, % 的数字运算,部分不同单位取值可以混合运算(没有明确)。

加法

可以省略加数的单位,默认和被加数一致:

$width: 100% + 10;

.container {
width: $width; // 110%
}

减法同理。

除法

/ 在 CSS 中通常起到分隔数字的用途,SassScript 作为 CSS 语言的拓展当然也支持这个功能,同时也赋予了 / 除法运算的功能。

以下三种情况 / 被视为除法运算符号:

  • 被圆括号包裹
  • 对变量、函数返回值进行除法运算
  • 结合其他运算符的算术表达式
$width: 1000px;

p {
font-size: 8px / 2; // 无效,并不等于 4px
font-size: (8px / 2); // 4px

width: $width / 2; // 500px
width: round(1.5px) / 2; // 1px

height: 5px + 8px / 2px; // 9px
}

如果不希望 / 对变量做除法,可以使用 #{} 插值将变量包裹起来:

p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}

注意: 对于乘法、除法运算,单位之间也会进行运算

  • 比如 2px * 2px 并不是我们想要的 4px,而是 4px^2,正确的应该是 2px * 2
  • (2px / 2px) 结果是 1 却不是 1px,正确应该是 2px / 2

字符串类型运算

+ 可以用于拼接字符串:

p {
cursor: e + -resize;
}

编译为:

p {
cursor: e-resize;
}

注意,如果有引号字符串(位于 + 左侧)连接无引号字符串,运算结果是有引号的,相反,无引号字符串(位于 + 左侧)连接有引号字符串,运算结果则没有引号。

p:before {
content: "Foo " + Bar;
font-family: sans- + "serif";
}

编译为:

p:before {
content: "Foo Bar";
font-family: sans-serif;
}

运算表达式与其他值连用时,用空格做连接符:

p {
margin: 3px + 4px auto;
}

编译为:

p {
margin: 7px auto;
}

逻辑运算符

andornot 对应与或非。

内置函数

概览

Sass 中内置了如下的函数,可以处理相关的数据:

  • sass:color
  • sass:list
  • sass:map
  • sass:math
  • sass:meta
  • sass:selector
  • sass:string

在数组数据类型介绍中,介绍了它的内置的函数,这里主要来介绍有关 map、color 和 math 相关的内置函数。

通用的内置函数

url()

传入一个 url,可以是相对路径和绝对路径,用于解析目标资源。

if($condition, $if-true, $if-false)

第一个参数添布尔类型表达式,结果是 true 则返回第二个参数值反之返回第三个参数值。

$a: 10px;
$b: 20px;

.container {
font-size: if($a > $b, $a, $b);
}

颜色相关

详见之前写的 blog:CSS颜色与Sass中的常用颜色函数

数学相关

占位 ... 待补充

继承

声明一个基类。

%base {
font-size: 16px;
width: 20px;
height: 20px;
}

我们就可以在其他选择器里面通过 @extends 继承里面的样式。

.content {
@extend %base; // 里面包含了 %base 的样式
}

@mixin

基本用法

在 mixin 内部可以定义一个样式片段,这个样式片段可以包含 Sass 中的所有特性:

@mixin large-text {
font: {
family: Arial;
size: 20px;
weight: bold;
}
color: #ff0000;
}

我们可以在需要使用这个样式片段的地方通过 @include 引入 mixin:

.container {
@include large-text;
padding: 4px;
margin-top: 10px;
}

当然也可以在全局作用域中引入 mixin:

@mixin silly-links {
a {
color: blue;
background-color: red;
}
}

@include silly-links;

也可以在 mixin 中引入其他 mixin:

@mixin compound {
@include highlighted-background;
@include header-text;
}

@mixin highlighted-background { background-color: #fc0; }
@mixin header-text { font-size: 20px; }

并且 mixin 的引入和它定义的先后无关,可以类比于 JS 中的 function。

参数传递

mixin 可以像 JS 的函数那样,传递参数:

@mixin sexy-border($color, $width) {
border: {
color: $color;
width: $width;
style: dashed;
}
}

p { @include sexy-border(blue, 1in); }

传递参数的时为了提高可读性,可以加上参数名:

p { @include sexy-border($color: blue, $width: 1in); }

并且,参数还可以设置默认值,如果 @include mixin 时,该参数没有传入,则会使用默认值:

@mixin sexy-border($color, $width: 1in) {
border: {
color: $color;
width: $width;
style: dashed;
}
}
p { @include sexy-border(blue); }
h1 { @include sexy-border(blue, 2in); }

有时传递的参数并不能确定其个数,我们在定义 mixin 时可以这样做:

@mixin box-shadow($shadows...) {
-moz-box-shadow: $shadows;
-webkit-box-shadow: $shadows;
box-shadow: $shadows;
}

.shadows {
@include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
}

对于 $shadows 就成了一个数组。

当然,通过 @include 调用 mixin 时,我们也可以通过扩展运算符来展开一个数组参数:

@mixin colors($text, $background, $border) {
color: $text;
background-color: $background;
border-color: $border;
}

$values: #ff0000, #00ff00, #0000ff;
.primary {
@include colors($values...);
}

@function

@function 的用法跟 @mixin 差不多,但是 @function 多了一个 @return,它可以更明确的确定要返回什么内容,@mixin 多用于返回样式片段。

语法:

// 定义函数
@function fn($param) {

// 函数体 ...

// 返回值
@return returnValue;
}

// 函数调用
.container {
width: fn(5); // 也可以这样:fn($param: 5)
}

@for

语法:@for $var from <start> through <end>,其中 $var 必须是一个变量,<start><end> 必须是整数。

@for $i from 1 through 3 {
.item-#{$i} { width: 2em * $i; }
}

最终会输出每次循环的内容,如下:

.item-1 { width: 2em; }
.item-2 { width: 4em; }
.item-3 { width: 6em; }

@each

语法:$var in <list>,其中 $var 必须是一个变量,表示每次循环时 <list> 中的元素,<list> 是列表,比如 (puma, sea-slug, egret, salamander) 就代表一个列表。

@each $animal in (puma, sea-slug, egret, salamander) {
.#{$animal}-icon {
background-image: url('/images/#{$animal}.png');
}
}

当然你也可以一次性声明多个变量对应多个列表:

@each $animal, $color, $cursor in (puma, black, default), (sea-slug, blue, pointer), (egret, white, move) {
.#{$animal}-icon {
background-image: url('/images/#{$animal}.png');
border: 2px solid $color;
cursor: $cursor;
}
}

只会循环输出 3 次,因为列表中的元素个数为 3 个,最终输出:

.puma-icon {
background-image: url('/images/puma.png');
border: 2px solid black;
cursor: default;
}
.sea-slug-icon {
background-image: url('/images/sea-slug.png');
border: 2px solid blue;
cursor: pointer;
}
.egret-icon {
background-image: url('/images/egret.png');
border: 2px solid white;
cursor: move;
}

另外,还可以遍历键值对,比如 (h1: 2em, h2: 1.5em, h3: 1.2em),里面每一个元素是 key: value 结构的,我们可以直接定义两个变量来遍历输出它:

@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
#{$header} {
font-size: $size;
}
}

@if

@if 表达式返回的不是 null 或者 false 时,条件成立,输出 {} 里面的内容

$blue-base: #1890ff;

@for $i from 1 through 10 {
// 1 ~ 6
@if $i <= 6 {
.blue-#{$i} {
background: tint($blue-base, (6 - $i) * 10);
}
}
// 6 ~ 10
@else {
.blue-#{$i} {
background: shade($blue-base, ($i - 6) * 10);
}
}
}